Skip to content

[fix] Load InterDisplay on production (headings were silently falling back to Inter) - #2836

Merged
joshestein merged 11 commits into
masterfrom
josh/2832-inter-display
Jul 31, 2026
Merged

[fix] Load InterDisplay on production (headings were silently falling back to Inter)#2836
joshestein merged 11 commits into
masterfrom
josh/2832-inter-display

Conversation

@joshestein

@joshestein joshestein commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Description

interDisplay (next/font, src/lib/fonts.ts) was imported only from _document.tsx. The pages router emits next/font CSS only for fonts reachable from _app, so --font-inter-display was never defined in production builds, only referenced. That made the .bluedot-h1/.bluedot-h2 rule in globals.css invalid at computed-value time, so every heading inherited --font-sans and silently rendered Inter. Local dev was unaffected, which is why it went unnoticed. Confirmed pre-fix: prod and staging both served CSS 89631b6d… with the variable referenced once and defined zero times.

Changes

  • _app.tsx imports interDisplay and applies interDisplay.variable, making the module reachable from the _app graph so its CSS ships. _document.tsx still puts both variable classes on <Html> — that's what lands the custom properties on :root, which globals.css needs for --font-sans. Both are load-bearing.
  • Dropped weight 700 from the next/font call: nothing renders .bluedot-h1/.bluedot-h2 at bold, and next/font preloads every declared weight. Ships 400/500/600.
  • adjustFontFallback: false, so the swap falls through to already-preloaded Inter instead of next/font's generated Arial-metric face. Inter and InterDisplay are the same typeface at two optical sizes, so that swap is far less visible.
  • Four homepage/lander headings hand-rolled .bluedot-h2's styling on a raw <h2> without the font-family, so they'd have stayed Inter while <H2> flipped everywhere else. Added bluedot-h2 and removed the classes that merely restated its defaults, keeping the real font-medium/tracking-tighter overrides.
  • Subsetted InterDisplay to latin + latin-ext, 106 kB → ~42 kB per weight. Without this the PR added 327 kB of preloaded font to every page; it now adds 130 kB.
  • Matched the homepage carousel heading ("Our community") to the other section headings. Its variant === 'homepage' branch stepped up to text-size-2xl above bd-md, rendering it at 56 px against 48 px for every other H2. .bluedot-h2 already supplies text-size-xl, so the size classes went entirely. Pre-existing, surfaced while auditing heading consistency for this change.

Font subsetting

Subsetted in place in public/fonts/, so the other apps that load these over HTTPS from bluedot.org/fonts (8 @font-face rules in libraries/ui/src/default-config/tailwind.css) get the smaller files too. That's deliberate: the library's .bluedot-h1 resolves to InterDisplay, var(--font-sans) and its --font-sans leads with full-coverage Inter, so any glyph outside the subset degrades to Inter — the same typeface at a different optical size, not a system font.

All four weights subsetted, including 700, which the website no longer references but the library still declares; leaving it full-coverage would make glyph support vary by weight.

The subset range, the exact regeneration command and the ss04 caveat are documented in apps/website/README.md under Font Inventory. Pre-subset originals are recoverable from 28b8c9fd9 if the range ever needs widening — re-run against those, not against already-subsetted files.

Two things worth knowing if you touch this:

  • --layout-features+= is required. I first wrote --layout-features='+ss04,…' and it silently emptied GSUB — ss04 gone, no error, no warning. Four of these headings set fontFeatureSettings: "'ss04' on", so that would have shipped as an invisible typography regression. The README documents a one-liner to verify ss04 survived.
  • Out-of-range scripts: Cyrillic/Greek fall back to Inter (full-coverage, already loaded). CJK falls to a system font, as it did before — Inter Display never covered it.

Testing

Verified against a local production build, with origin/master built and served alongside it so before/after differ by this change alone.

  • --font-inter-display:"interDisplay" now defined, with no "interDisplay Fallback" companion (confirms adjustFontFallback: false).
  • 3 @font-face at 400/500/600; 7 font preloads on /about (4 Inter + 3 InterDisplay), totalling 573 kB vs 443 kB on master.
  • Rendered fonts via Chrome DevTools CSS.getPlatformFontsForNode — what actually painted, not what was requested:
Page Heading Weight Before After
/about, /our-community, /programs/advising H1 400 Inter Inter Display
/programs/advising H2 600 Inter Inter Display SemiBold
/ 7 section H2s 500 Inter Medium Inter Display Medium
/ hero H1 400 Inter Inter (unchanged, deliberate pin)
  • Subsetting verified two ways. Rendering the original and subsetted files side by side across all four weights with ss04 enabled, over the real heading copy plus latin-ext and ss04-sensitive glyphs: 0 differing pixels out of 6.72M. And re-running the rendered-font check against the served build with requests to bluedot.org blocked, so nothing could be served by production's still-full copy: 0 blocked requests, weights 400/500/600 all resolving to Inter Display faces. All four subsetted files confirmed to carry ss04 and ss02.
  • The screenshots below were captured pre-subset; the 0-pixel diff is why they remain accurate.
  • Full suite green (127 files / 1077 tests), lint + typecheck clean. No snapshots reference the changed class strings.
  • Homepage heading audit across 390/680/768/1024/1280/1440/1920: all 7 H2s uniform at every breakpoint (32 px below bd-md, 48 px above). The 66 px horizontal overflow at 1024 px is pre-existing header behaviour — staging on master shows the identical overflow from the same nav-cta/dropdown elements.

No regression test added — nothing stops a future _app cleanup or Next upgrade from silently reverting this, and the failure mode is invisible in dev.

Deployment

⚠️ This flips every heading on the site. Merging goes to staging only; the next website/vX.Y.Z release will carry a sitewide visual change, so please don't cut one before design sign-off.

Note the subsetted fonts reach the other apps on the website's production release, not on their own deploys, since they fetch from bluedot.org/fonts at runtime.

Two things this makes visible for the first time, both worth a designer's eye:

  • prose sets no heading font-family and <H1>/<H2> carry not-prose, so blog and markdown course-content headings stay Inter while chrome/marketing headings become InterDisplay.
  • The homepage hero stays pinned to Inter (deliberate, HomeHeroContent.tsx), making it the only Inter hero on the site.

Latent before, live now: the globals.css heading rule falls back to Tailwind's stock serif stack, so headings would render Times if both webfonts failed. Left alone to keep this diff minimal.

Inter itself is still unsubsetted (443 kB across 4 weights). Subsetting it is the bigger win but carries real glyph risk — it renders user-generated names and its fallback is a system font, not a sibling cut — so it belongs in its own PR.

Issue

Fixes #2832

Developer checklist

Screenshot

Zoomed crops first, since at full-page scale the difference reads as slightly tighter letterforms rather than anything obvious.

Zoomed crop Before After
/about H1 (weight 400)
Homepage section H2 (weight 500)
📸 Before After
📱
🖥️
🖥️ home

The logo carousel differs between the homepage shots only because it's an infinite-scroll animation caught at a different frame.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The website now loads interDisplay from _app.tsx and applies its variable class to the application root. The font configuration no longer loads the Inter Display Bold file or generates an Arial fallback. Four homepage and testimonial headings now use the shared bluedot-h2 class.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation meets the coding objectives in issue #2832 by loading InterDisplay through _app and preserving the homepage hero’s Inter override.
Out of Scope Changes check ✅ Passed All changes support issue #2832, including font configuration and applying shared heading styles to affected raw h2 elements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly summarises the main change: loading InterDisplay in production for headings.
Description check ✅ Passed The description is complete and covers the changes, issue, testing, deployment impact, checklist, and screenshots.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch josh/2832-inter-display

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Will-Howard
Will-Howard temporarily deployed to josh/2832-inter-display - bluedot-storybook-preview PR #2836 July 31, 2026 11:00 — with Render Destroyed
@joshestein joshestein changed the title Inter display for h1/h2 [fix] Load InterDisplay on production (headings were silently falling back to Inter) Jul 31, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@joshestein

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@joshestein
joshestein marked this pull request as ready for review July 31, 2026 11:04
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR ensures Inter Display is included in production builds and consistently applied to marketing headings.

  • Imports and applies the Inter Display variable from the shared application shell.
  • Removes the unused 700-weight preload and disables the generated Arial-metric fallback.
  • Applies the shared bluedot-h2 styling to four headings while preserving their existing weight, tracking, sizing, color, and responsive overrides.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete regressions identified in the changed font-loading or heading-style paths.

The application-level font registration reaches every page layout, the removed font weight has no current Inter Display consumers, and the four class replacements preserve their prior computed styles apart from the intended font-family change.

Important Files Changed

Filename Overview
apps/website/src/pages/_app.tsx Adds Inter Display to the Pages Router application graph and applies its variable class to the wrapper around every layout path.
apps/website/src/lib/fonts.ts Removes the currently unused 700-weight Inter Display source and makes swaps fall through to the existing Inter face.
apps/website/src/components/homepage/CourseValueProps.tsx Adopts bluedot-h2 while preserving the heading’s prior computed utility styles.
apps/website/src/components/homepage/EventsSection.tsx Adopts bluedot-h2 without changing the heading’s existing size, weight, spacing, color, or width behavior.
apps/website/src/components/homepage/MergedLadder.tsx Adopts bluedot-h2 while retaining the existing medium-weight and tighter-tracking overrides.
apps/website/src/components/lander/TestimonialCarousel.tsx Adds bluedot-h2 to the homepage heading while preserving its responsive sizing and explicit typography overrides.

Reviews (1): Last reviewed commit: "[chore] Remove PR screenshots (SHA-pinne..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/website/src/pages/_app.tsx (1)

18-18: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Verify the global font preload cost before release.

Importing interDisplay from _app.tsx makes all configured font weights available to every route. The PR reports a 327 kB increase in preloaded fonts per page. Measure representative production-like routes and reduce the preload scope if pages that do not use these headings still preload all three assets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/website/src/pages/_app.tsx` at line 18, Verify the global font preload
behavior caused by importing interDisplay in _app.tsx by measuring
representative production-like routes and their loaded font assets. If routes
without heading usage preload all configured interDisplay weights, narrow the
import or font configuration scope so only pages that use those headings preload
them, while preserving inter and required heading rendering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/website/src/pages/_app.tsx`:
- Line 18: Verify the global font preload behavior caused by importing
interDisplay in _app.tsx by measuring representative production-like routes and
their loaded font assets. If routes without heading usage preload all configured
interDisplay weights, narrow the import or font configuration scope so only
pages that use those headings preload them, while preserving inter and required
heading rendering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d716d98-898f-4acb-9c00-93f5e22eb086

📥 Commits

Reviewing files that changed from the base of the PR and between 8fdccb5 and 28b8c9f.

📒 Files selected for processing (6)
  • apps/website/src/components/homepage/CourseValueProps.tsx
  • apps/website/src/components/homepage/EventsSection.tsx
  • apps/website/src/components/homepage/MergedLadder.tsx
  • apps/website/src/components/lander/TestimonialCarousel.tsx
  • apps/website/src/lib/fonts.ts
  • apps/website/src/pages/_app.tsx

joshestein and others added 2 commits July 31, 2026 13:30
Takes each weight from ~106kB to ~42kB, cutting this PR's added preload
from 327kB to 130kB. Subsetted in place, so the other apps loading these
over HTTPS from bluedot.org get the smaller files too; glyphs outside the
range fall back to Inter, which is full-coverage and already in the chain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@joshestein

Copy link
Copy Markdown
Collaborator Author

Added font subsetting: InterDisplay latin + latin-ext, 106kB → ~42kB per weight, cutting this PR's added preload from 327kB to 130kB. Subsetted in place so the other apps loading these from bluedot.org/fonts benefit too; out-of-range glyphs degrade to Inter, which is full-coverage and already in the chain.

Verified by pixel-diffing rendered headings before/after subsetting: 0 differing pixels, so ss04 survived. Worth flagging for anyone regenerating these — --layout-features='+ss04' silently empties GSUB with no error; the correct syntax is --layout-features+=ss04. apps/website/scripts/subset-fonts.sh asserts ss04 is present and fails if not.

@claude review

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Will-Howard
Will-Howard temporarily deployed to josh/2832-inter-display - bluedot-preview PR #2836 July 31, 2026 11:45 — with Render Destroyed
@Will-Howard
Will-Howard temporarily deployed to josh/2832-inter-display - bluedot-storybook-preview PR #2836 July 31, 2026 11:45 — with Render Destroyed
@joshestein
joshestein merged commit 36eacd5 into master Jul 31, 2026
7 checks passed
@joshestein
joshestein deleted the josh/2832-inter-display branch July 31, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Load InterDisplay on production (headings silently fall back to Inter)

2 participants